package com.songaw.generator.modules.auths.security;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.songaw.generator.common.conf.redis.RedisUtil;
import com.songaw.generator.common.constant.Constant;
import io.jsonwebtoken.*;
import io.jsonwebtoken.impl.DefaultClock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.stereotype.Component;

import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

@Slf4j
@Component
public class JwtTokenUtil implements Serializable {

    static final String CLAIM_KEY_USERNAME = "sub";
    static final String CLAIM_KEY_CREATED = "iat";
    private static final long serialVersionUID = -3301605591108950415L;
    //@SuppressFBWarnings(value = "SE_BAD_FIELD", justification = "It's okay here")
    private Clock clock = DefaultClock.INSTANCE;

    @Value("${jwt.secret}")
    private String secret;
    //    token失效时间为5分钟
    @Value("${jwt.expiration}")
    private Long expiration;
    //token失效后 中间间隔2分钟是有效的
    @Value("${jwt.refresh_token_expiration}")
    private Long refreshTokenExpiration;
    //redis中token失效时间为1小时
    @Value("${jwt.redis_token_expiration}")
    private Long redisTokenExpiration;

    public String getSubject(String token) {
        return getClaimFromToken(token, Claims::getSubject);
    }
    //得到token 发布日期
    public Date getIssuedAtDateFromToken(String token) {
        return getClaimFromToken(token, Claims::getIssuedAt);
    }
    //得到token超时日期
    public Date getExpirationDateFromToken(String token) {
        return getClaimFromToken(token, Claims::getExpiration);
    }

    public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = getAllClaimsFromToken(token);
        return claimsResolver.apply(claims);
    }

    private Claims getAllClaimsFromToken(String token) {
        try {
            return Jwts.parser().setAllowedClockSkewSeconds(refreshTokenExpiration)
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (ExpiredJwtException | SignatureException e) {
            throw new BadCredentialsException(e.getMessage(), e);
        } catch (UnsupportedJwtException | MalformedJwtException e) {
            throw new AuthenticationServiceException(e.getMessage(), e);
        } catch (IllegalArgumentException e) {
            throw new InternalAuthenticationServiceException(e.getMessage(), e);
        }

    }

    private Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(clock.now());
    }

    /*private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
        return (lastPasswordReset != null && created.before(lastPasswordReset));
    }*/

    private Boolean ignoreTokenExpiration(String token) {
        // here you specify tokens, for that the expiration is ignored
        return false;
    }

    public String generateToken(String key) {
        Map<String, Object> claims = new HashMap<>();
        final Date createdDate = clock.now();
        final Date expirationDate = calculateExpirationDate(createdDate);
        final Date refreshTokenExpirationDate = calculateRefreshTokenExpirationDate(expirationDate);
        System.out.println("token 创建时间"+JSON.toJSONString(createdDate, SerializerFeature.WriteDateUseDateFormat));
        System.out.println("token 失效时间"+JSON.toJSONString(expirationDate, SerializerFeature.WriteDateUseDateFormat));
        System.out.println("token 失效-刷新过期时间"+JSON.toJSONString(refreshTokenExpirationDate, SerializerFeature.WriteDateUseDateFormat));
        System.out.println("redis 过期时间"+JSON.toJSONString(new Date(createdDate.getTime() + redisTokenExpiration * 1000), SerializerFeature.WriteDateUseDateFormat));


        String token =  Jwts.builder()
                .setClaims(claims)
                .setSubject(key)
                .setIssuedAt(createdDate)
                .setExpiration(expirationDate)
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
        try {
            RedisUtil.del(Constant.CODE_CACHE_TOKEN_USERNAME + "_" + key);
            RedisUtil.lSet(Constant.CODE_CACHE_TOKEN_USERNAME + "_" + key,token,redisTokenExpiration);
        }catch (Exception e){
            e.printStackTrace();
        }
        return token;


    }



    public String refreshToken(String token) {

        final Date createdDate = clock.now();
        final Date expirationDate = calculateExpirationDate(createdDate);
        final Claims claims = getAllClaimsFromToken(token);
        claims.setIssuedAt(createdDate);
        claims.setExpiration(expirationDate);
        final String buildToken= Jwts.builder()
                .setClaims(claims)
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
        try {
            String key = claims.getSubject();
            Long size =  RedisUtil.lGetListSize(Constant.CODE_CACHE_TOKEN_USERNAME + "_" + key);
            if(size>=5){
                //最多存100个token
                RedisUtil.lRemove(Constant.CODE_CACHE_TOKEN_USERNAME + "_" + key,0,RedisUtil.lGetIndex(Constant.CODE_CACHE_TOKEN_USERNAME + "_" + key,0));
            }
            RedisUtil.lSet(Constant.CODE_CACHE_TOKEN_USERNAME + "_" + key, buildToken, redisTokenExpiration);

        }catch (Exception e){
            e.printStackTrace();
        }
        return buildToken;
    }
    //验证token
    public Boolean validateToken(String token,String key) {

        final String tokenKey = getSubject(token);
        final Date created = getIssuedAtDateFromToken(token);
        //final Date expiration = getExpirationDateFromToken(token);
        return (
                tokenKey.equals(key)
                        && !isTokenExpired(token)
        );
    }
    //计算到期日
    private Date calculateExpirationDate(Date createdDate) {
        return new Date(createdDate.getTime() + expiration * 1000);
    }
    //计算刷新日期
    private Date calculateRefreshTokenExpirationDate(Date expirationDate) {
        return new Date(expirationDate.getTime() + refreshTokenExpiration * 1000);
    }
}